﻿// <copyright file="CompareUtil.cs" company="Lu Studio">
// Copyright (c) Lu Studio. All rights reserved.
// </copyright>
// <author>lu qi</author>
// <createTime>2021-09-30</createTime>

namespace ElfKit.Common
{
    using ElfKit.Common.Properties;
    using System;
    using System.Collections;
    using System.Collections.Generic;

    /// <summary>
    /// 比较工具
    /// </summary>
    public static class CompareUtil
    {
        #region AreEqual

        /// <summary>
        /// 判断<paramref name="current"/>与<paramref name="other"/>按委托<paramref name="equivalence"/>是否相等.
        /// </summary>
        /// <typeparam name="T">比较对象的类型.</typeparam>
        /// <param name="current">当前对象.</param>
        /// <param name="other">比较对象.</param>
        /// <param name="equivalence"><see cref="Equivalence{T}"/>实例,用于<paramref name="current"/>与<paramref name="other"/>都不为null时的相等比较.</param>
        /// <returns>true:相等 false:不相等.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="equivalence"/>为null.</exception>
        public static bool AreEqual<T>(T current, T other, Equivalence<T> equivalence)
        {
            Guard.NotNull(equivalence, nameof(equivalence));
            ////是否引用相等
            if (ReferenceEquals(current, other))
            {
                return true;
            }
            ////如都不为null执行委托比较是否相等
            return !(current is null) && !(other is null) && equivalence(current, other);
        }

        /// <summary>
        /// 判断<paramref name="current"/>与<paramref name="other"/>按委托<paramref name="equivalence"/>是否相等.
        /// </summary>
        /// <typeparam name="T">比较对象的类型.</typeparam>
        /// <param name="current">当前对象.</param>
        /// <param name="other">比较对象.</param>
        /// <param name="equivalence"><see cref="Equivalence{T}"/>实例,用于<paramref name="current"/>与<paramref name="other"/>都不为null时的相等比较.</param>
        /// <returns>true:相等 false:不相等.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="equivalence"/>为null.</exception>
        /// <exception cref="InvalidOperationException"><paramref name="current"/>与<paramref name="other"/>类型不同.</exception>
        public static bool AreEqual<T>(object current, object other, Equivalence<T> equivalence)
        {
            Guard.NotNull(equivalence, nameof(equivalence));
            ////是否引用相等
            if (ReferenceEquals(current, other))
            {
                return true;
            }

            if (current is null
             || other is null)
            {
                return false;
            }

            T lt = Convert<T>(current, nameof(current));
            T rt = Convert<T>(other, nameof(other));
            return equivalence(lt, rt);
        }

        #endregion

        #region CompareTo

        /// <summary>
        /// 使用<paramref name="comparison"/>委托比较<paramref name="current"/>与<paramref name="other"/>并获得比较结果.
        /// </summary>
        /// <typeparam name="T">比较对象的类型.</typeparam>
        /// <param name="current">当前对象.</param>
        /// <param name="other">比较对象.</param>
        /// <param name="comparison"><see cref="Comparison{T}"/>实例,用于<paramref name="current"/>与<paramref name="other"/>都不为null时的大小比较.</param>
        /// <returns>比较结果.</returns>
        /// <remarks>
        /// <para><paramref name="current"/>大于<paramref name="other"/>返回<see cref="Constant.CompareResult.GreaterThan"/></para>
        /// <para><paramref name="current"/>小于<paramref name="other"/>返回<see cref="Constant.CompareResult.LessThan"/></para>
        /// <para><paramref name="current"/>等于<paramref name="other"/>返回<see cref="Constant.CompareResult.Equal"/></para>
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="comparison"/>为null.</exception>
        public static int CompareTo<T>(T current, T other, Comparison<T> comparison)
        {
            Guard.NotNull(comparison, nameof(comparison));
            ////是否引用相等
            if (ReferenceEquals(current, other))
            {
                return Constant.CompareResult.Equal;
            }
            ////是否值类型
            if (typeof(T).IsValueType)
            {
                return comparison(current, other);
            }
            ////当前为null 其他不为null 当前大于其他
            if (current is null && !(other is null))
            {
                return Constant.CompareResult.LessThan;
            }
            ////当前不为null 其他为null 当前小于其他
            if (!(current is null) && other is null)
            {
                return Constant.CompareResult.GreaterThan;
            }
            ////当前不为null 其他不为null 执行比较委托
            return comparison(current, other);
        }

        /// <summary>
        /// 使用<paramref name="comparison"/>委托比较<paramref name="current"/>与<paramref name="other"/>并获得比较结果.
        /// </summary>
        /// <typeparam name="T">比较对象的类型.</typeparam>
        /// <param name="current">当前对象.</param>
        /// <param name="other">比较对象.</param>
        /// <param name="comparison"><see cref="Comparison{T}"/>实例,用于<paramref name="current"/>与<paramref name="other"/>都不为null时的大小比较.</param>
        /// <returns>比较结果.</returns>
        /// <remarks>
        /// <para><paramref name="current"/>大于<paramref name="other"/>返回<see cref="Constant.CompareResult.GreaterThan"/></para>
        /// <para><paramref name="current"/>小于<paramref name="other"/>返回<see cref="Constant.CompareResult.LessThan"/></para>
        /// <para><paramref name="current"/>等于<paramref name="other"/>返回<see cref="Constant.CompareResult.Equal"/></para>
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="comparison"/>为null.</exception>
        /// <exception cref="ArgumentException"><paramref name="other"/> 不是<typeparamref name="T"/>类型.</exception>
        public static int CompareTo<T>(T current, object other, Comparison<T> comparison)
        {
            Guard.NotNull(comparison, nameof(comparison));
            ////是否引用相等
            if (ReferenceEquals(current, other))
            {
                return Constant.CompareResult.Equal;
            }
            ////是否值类型
            if (typeof(T).IsValueType)
            {
                if (other is T t)
                {
                    return comparison(current, t);
                }

                return other is null
                    ? Constant.CompareResult.GreaterThan
                    : throw new ArgumentException(Localization.Format<Resources>(nameof(Resources.CannotCompare), typeof(T).FullName), nameof(other));
            }
            ////当前为null 其他不为null 当前大于其他
            if (current is null && !(other is null))
            {
                return Constant.CompareResult.LessThan;
            }
            ////当前不为null 其他为null 当前小于其他
            if (!(current is null) && other is null)
            {
                return Constant.CompareResult.GreaterThan;
            }
            ////当前不为null 其他不为null 执行比较委托
            return other is T tt
                ? comparison(current, tt)
                : throw new ArgumentException(Localization.Format<Resources>(nameof(Resources.CannotCompare), typeof(T).FullName), nameof(other));
        }

        #endregion

        #region AreEnumerableEqual

        /// <summary>
        /// 判断枚举对象<paramref name="first"/>与<paramref name="second"/>按委托<paramref name="equivalence"/>是否相等.
        /// </summary>
        /// <typeparam name="T">比较对象的类型.</typeparam>
        /// <param name="first">当前<see cref="IEnumerable{T}"/>对象.</param>
        /// <param name="second">比较<see cref="IEnumerable{T}"/>对象.</param>
        /// <param name="equivalence"><see cref="Equivalence{T}"/>实例,用于<paramref name="first"/>与<paramref name="second"/>都不为null时的相等比较.</param>
        /// <returns>true:相等 false:不相等.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="equivalence"/>为null.</exception>
        public static bool AreEnumerableEqual<T>(IEnumerable<T> first, IEnumerable<T> second, Equivalence<T> equivalence)
        {
            Guard.NotNull(equivalence, nameof(equivalence));

            if (TryBasalAreEqual(first, second, out bool result))
            {
                return result;
            }
            ////比较每个项目
            using IEnumerator<T> enumeratorFirst = first.GetEnumerator();
            using IEnumerator<T> enumeratorSecond = second.GetEnumerator();
            return HandleAreEnumerableEqual(
                () => enumeratorFirst.MoveNext(),
                () => enumeratorSecond.MoveNext(),
                () => AreEqual(enumeratorFirst.Current, enumeratorSecond.Current, equivalence));
        }

        /// <summary>
        /// 判断枚举对象<paramref name="first"/>与<paramref name="second"/>按委托<paramref name="equivalence"/>是否相等.
        /// </summary>
        /// <typeparam name="T">比较对象的类型.</typeparam>
        /// <param name="first">当前<see cref="IEnumerable"/>对象.</param>
        /// <param name="second">比较<see cref="IEnumerable"/>对象.</param>
        /// <param name="equivalence"><see cref="Equivalence{T}"/>实例,用于<paramref name="first"/>与<paramref name="second"/>都不为null时的相等比较.</param>
        /// <returns>true:相等 false:不相等.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="equivalence"/>为null.</exception>
        public static bool AreEnumerableEqual<T>(IEnumerable first, IEnumerable second, Equivalence<T> equivalence)
        {
            Guard.NotNull(equivalence, nameof(equivalence));

            if (TryBasalAreEqual(first, second, out bool result))
            {
                return result;
            }
            ////比较每个项目
            IEnumerator enumeratorFirst = first.GetEnumerator();
            IEnumerator enumeratorSecond = second.GetEnumerator();
            return HandleAreEnumerableEqual(
                () => enumeratorFirst.MoveNext(),
                () => enumeratorSecond.MoveNext(),
                () => AreEqual(enumeratorFirst.Current, enumeratorSecond.Current, equivalence));
        }

        #endregion

        #region EnumerableCompareTo

        /// <summary>
        /// 使用<paramref name="comparison"/>委托比较<paramref name="current"/>与<paramref name="other"/>并获得比较结果.
        /// </summary>
        /// <typeparam name="T">比较对象的类型.</typeparam>
        /// <param name="current">当前对象.</param>
        /// <param name="other">比较对象.</param>
        /// <param name="comparison"><see cref="Comparison{T}"/>实例,用于<paramref name="current"/>与<paramref name="other"/>都不为null时的大小比较.</param>
        /// <returns>比较结果.</returns>
        /// <remarks>
        /// <para><paramref name="current"/>大于<paramref name="other"/>返回<see cref="Constant.CompareResult.GreaterThan"/></para>
        /// <para><paramref name="current"/>小于<paramref name="other"/>返回<see cref="Constant.CompareResult.LessThan"/></para>
        /// <para><paramref name="current"/>等于<paramref name="other"/>返回<see cref="Constant.CompareResult.Equal"/></para>
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="comparison"/>为null.</exception>
        public static int EnumerableCompareTo<T>(IEnumerable<T> current, IEnumerable<T> other, Comparison<T> comparison)
        {
            Guard.NotNull(comparison, nameof(comparison));

            if (TryBasalCompareTo(current, other, out int result))
            {
                return result;
            }
            ////比较每个项目
            using (IEnumerator<T> enumeratorCurrent = current.GetEnumerator())
            using (IEnumerator<T> enumeratorOther = other.GetEnumerator())
            {
                return HandleEnumerableCompareTo(
                    () => enumeratorCurrent.MoveNext(),
                    () => enumeratorOther.MoveNext(),
                    () => CompareTo(enumeratorCurrent.Current, enumeratorOther.Current, comparison));
            }
        }

        /// <summary>
        /// 使用<paramref name="comparison"/>委托比较<paramref name="current"/>与<paramref name="other"/>并获得比较结果.
        /// </summary>
        /// <typeparam name="T">比较对象的类型.</typeparam>
        /// <param name="current">当前对象.</param>
        /// <param name="other">比较对象.</param>
        /// <param name="comparison"><see cref="Comparison{T}"/>实例,用于<paramref name="current"/>与<paramref name="other"/>都不为null时的大小比较.</param>
        /// <returns>比较结果.</returns>
        /// <remarks>
        /// <para><paramref name="current"/>大于<paramref name="other"/>返回<see cref="Constant.CompareResult.GreaterThan"/></para>
        /// <para><paramref name="current"/>小于<paramref name="other"/>返回<see cref="Constant.CompareResult.LessThan"/></para>
        /// <para><paramref name="current"/>等于<paramref name="other"/>返回<see cref="Constant.CompareResult.Equal"/></para>
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="comparison"/>为null.</exception>
        public static int EnumerableCompareTo<T>(IEnumerable<T> current, IEnumerable other, Comparison<T> comparison)
        {
            Guard.NotNull(comparison, nameof(comparison));

            if (TryBasalCompareTo(current, other, out int result))
            {
                return result;
            }
            ////比较每个项目
            using (IEnumerator<T> enumeratorCurrent = current.GetEnumerator())
            {
                IEnumerator enumeratorOther = other.GetEnumerator();
                return HandleEnumerableCompareTo(
                    () => enumeratorCurrent.MoveNext(),
                    () => enumeratorOther.MoveNext(),
                    () => CompareTo(enumeratorCurrent.Current, enumeratorOther.Current, comparison));
            }
        }

        #endregion

        #region HashCodeAreEqual

        /// <summary>
        /// 判断<paramref name="current"/>与<paramref name="other"/>按<paramref name="getHashCode"/>获得哈希编码是否相等.
        /// </summary>
        /// <typeparam name="T">比较对象的类型.</typeparam>
        /// <param name="current">当前对象.</param>
        /// <param name="other">比较对象.</param>
        /// <param name="getHashCode">获得哈希编码委托.</param>
        /// <returns>true:相等 false:不相等.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="getHashCode"/>为null.</exception>
        public static bool HashCodeAreEqual<T>(T current, T other, Func<T, int> getHashCode)
        {
            Guard.NotNull(getHashCode, nameof(getHashCode));

            int leftHashCode = getHashCode(current);
            int rightHashCode = getHashCode(other);

            return leftHashCode == rightHashCode;
        }

        /// <summary>
        /// 判断<paramref name="current"/>与<paramref name="other"/>按<paramref name="getHashCode"/>获得哈希编码是否相等.
        /// </summary>
        /// <typeparam name="T">比较对象的类型.</typeparam>
        /// <param name="current">当前对象.</param>
        /// <param name="other">比较对象.</param>
        /// <param name="getHashCode">获得哈希编码委托.</param>
        /// <returns>true:相等 false:不相等.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="getHashCode"/>为null.</exception>
        public static bool HashCodeAreEqual<T>(T current, object other, Func<T, int> getHashCode)
        {
            Guard.NotNull(getHashCode, nameof(getHashCode));

            int leftHashCode = getHashCode(current);
            int rightHashCode = other is T t
                ? getHashCode(t)
                : throw new ArgumentException(Localization.Format<Resources>(nameof(Resources.CannotCompare), typeof(T).FullName), nameof(other));
            return leftHashCode == rightHashCode;
        }

        #endregion

        #region 内部函数

        /// <summary>
        /// 把<paramref name="obj"/>转换为<typeparamref name="T"/>类型,如果无法转换则抛出<see cref="InvalidOperationException"/>异常.
        /// </summary>
        /// <typeparam name="T">类型.</typeparam>
        /// <param name="obj">对象.</param>
        /// <param name="argName">参数名称.</param>
        /// <returns>转换后的<typeparamref name="T"/>对象.</returns>
        /// <exception cref="InvalidOperationException">无法转换.</exception>
        private static T Convert<T>(object obj, string argName)
        {
            if (!(obj is T work))
            {
                throw new InvalidOperationException(Localization.Format<Resources>(
                    nameof(Resources.CannotConvertToType),
                    typeof(T).FullName,
                    argName));
            }

            return work;
        }

        /// <summary>
        /// 尝试基础比较个对象的是否相等.
        /// </summary>
        /// <param name="current">当前对象.</param>
        /// <param name="other">另一个对象.</param>
        /// <param name="result">比较结果.</param>
        /// <returns>true:成功 false:失败.</returns>
        private static bool TryBasalAreEqual(object current, object other, out bool result)
        {
            result = default;
            if (ReferenceEquals(current, other))
            {
                result = true;
                return true;
            }
            ////当前为null 或 另一个为null
            if (current is null || other is null)
            {
                result = false;
                return true;
            }

            return false;
        }

        /// <summary>
        /// 尝试基础比较个对象的大小.
        /// </summary>
        /// <param name="current">当前对象.</param>
        /// <param name="other">另一个对象.</param>
        /// <param name="result">比较结果.</param>
        /// <returns>true:成功 false:失败.</returns>
        private static bool TryBasalCompareTo(object current, object other, out int result)
        {
            result = default;
            ////是否引用相等
            if (ReferenceEquals(current, other))
            {
                result = Constant.CompareResult.Equal;
                return true;
            }
            ////当前为null 其他不为null 当前大于其他
            if (current is null && !(other is null))
            {
                result = Constant.CompareResult.LessThan;
                return true;
            }
            ////当前不为null 其他为null 当前小于其他
            if (!(current is null) && other is null)
            {
                result = Constant.CompareResult.GreaterThan;
                return true;
            }

            return false;
        }

        /// <summary>
        /// 处理判断两个枚举对象是否相等.
        /// </summary>
        /// <param name="getCanCurrentMoveNext">当前枚举对象是否能移动到下一个委托.</param>
        /// <param name="getCanOtherMoveNext">另一个枚举对象是否能移动到下一个委托.</param>
        /// <param name="areElementEqual">两个枚举对象的当前项目是否相等委托.</param>
        /// <returns>true：是 false:否.</returns>
        private static bool HandleAreEnumerableEqual(
            Func<bool> getCanCurrentMoveNext,
            Func<bool> getCanOtherMoveNext,
            Func<bool> areElementEqual)
        {
            bool canCurrentMoveNext = getCanCurrentMoveNext();
            bool canOtherMoveNext = getCanOtherMoveNext();

            if (canCurrentMoveNext != canOtherMoveNext)
            {
                return false;
            }

            while (canCurrentMoveNext && canOtherMoveNext)
            {
                if (!areElementEqual())
                {
                    return false;
                }

                canCurrentMoveNext = getCanCurrentMoveNext();
                canOtherMoveNext = getCanOtherMoveNext();

                if (canCurrentMoveNext != canOtherMoveNext)
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 处理比较两个枚举对象.
        /// </summary>
        /// <param name="getCanCurrentMoveNext">当前枚举对象是否能移动到下一个委托.</param>
        /// <param name="getCanOtherMoveNext">另一个枚举对象是否能移动到下一个委托.</param>
        /// <param name="elementCompareTo">比较两个枚举对象的当前项目委托.</param>
        /// <returns>true：是 false:否.</returns>
        private static int HandleEnumerableCompareTo(
            Func<bool> getCanCurrentMoveNext,
            Func<bool> getCanOtherMoveNext,
            Func<int> elementCompareTo)
        {
            bool canCurrentMoveNext = getCanCurrentMoveNext();
            bool canOtherMoveNext = getCanOtherMoveNext();

            if (canCurrentMoveNext && !canOtherMoveNext)
            {
                return Constant.CompareResult.GreaterThan;
            }

            if (!canCurrentMoveNext && canOtherMoveNext)
            {
                return Constant.CompareResult.LessThan;
            }

            while (canCurrentMoveNext && canOtherMoveNext)
            {
                int result = elementCompareTo();
                if (result != Constant.CompareResult.Equal)
                {
                    return result;
                }

                canCurrentMoveNext = getCanCurrentMoveNext();
                canOtherMoveNext = getCanOtherMoveNext();

                if (canCurrentMoveNext && !canOtherMoveNext)
                {
                    return Constant.CompareResult.GreaterThan;
                }

                if (!canCurrentMoveNext && canOtherMoveNext)
                {
                    return Constant.CompareResult.LessThan;
                }
            }

            return Constant.CompareResult.Equal;
        }
    }

    #endregion
}